<?php
/**
 * Library Of Shared Code (LOSC)
 *
 * LICENSE
 *
 * This source file is subject to the New BSD License that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @category   LOSC Framework
 * @package    Util
 * @copyright  Copyright (c) 2008 Robin Skoglund (http://robinsk.net/)
 * @license    http://creativecommons.org/licenses/BSD/  New BSD License
 */

/**
 * Used for instantiating and retrieving arbitrary objects
 * 
 * The reason for using the object store is to prevent frequently used objects
 * from being instantiated more than once, for example validators and filters.
 *
 * @category   LOSC Framework
 * @package    Util
 * @copyright  Copyright (c) 2008 Robin Skoglund (http://robinsk.net/)
 * @license    http://creativecommons.org/licenses/BSD/  New BSD License
 */
class Losc_Util_ObjectStore
{
    /**
     * Contains object instances
     *
     * @var array
     */
    protected static $_instances = array();
    
    /**
     * Retrieves an object of type $class constructed with the given params
     *
     * @param  string $class               class name
     * @param  array  $constructorParams   [optional] constructor parameters
     * @return object                      object of type $class
     * @throws Zend_Exception              if unable to load the class
     * @throws ReflectionException         if reflection fails
     */
    public static function get($class, array $constructorParams = array())
    {
        if (!isset(self::$_instances[$class])) {
            self::$_instances[$class] = array();
        }
        
        $hash = self::_hash($constructorParams);
        
        if (!isset(self::$_instances[$class][$hash])) {
            self::$_instances[$class][$hash] = 
                self::_instantiate($class, $constructorParams, $hash);
        }
        
        return self::$_instances[$class][$hash];
    }
    
    /**
     * Returns a hash of the given params
     *
     * @param  mixed $params
     * @return string
     */
    protected static function _hash($params)
    {
        return md5(serialize($params));
    }
    
    /**
     * Instantiates class
     *
     * @param  string $className   class name
     * @param  array  $params      constructor params
     * @throws Zend_Exception      if unable to load the class
     * @throws ReflectionException if reflection fails
     * @return void
     */
    public static function _instantiate($className, $params)
    {
        // load the class
        Zend_Loader::loadClass($className);
        
        // reflect the class
        $class = new ReflectionClass($className);
        
        // get constructor, if any
        if ($constructor = $class->getConstructor()) {
            // get constructor parameters
            $constructorParams = $constructor->getParameters();
        
            // actual params to use when instantiating
            $actualParams = array();
            
            // normalize given params ("merge" with constructor params)
            foreach ($constructorParams as $param) {
                $name = $param->getName();
                
                if (array_key_exists($name, $params)) {
                    // param given by name
                    $actualParams[] = $params[$name];
                } elseif (array_key_exists($param->getPosition(), $params)) {
                    // param given by position
                    $actualParams[] = $params[$param->getPosition()];
                } elseif ($param->isDefaultValueAvailable()) {
                    // param not given, but a default value is defined
                    $actualParams[] = $param->getDefaultValue();
                } elseif ($param->allowsNull()) {
                    // param not given, but allows null
                    $actualParams[] = null;
                } elseif ($param->isArray()) {
                    // param not given, but should be array
                    $actualParams[] = array();
                }
            }
            
            // instantiate with actual params
            $instance = $class->newInstanceArgs($actualParams);
        } else {
            // class has no constructor specified, so instantiate directly
            $instance = new $className();
        }
        
        return $instance;
    }
}
